{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Token types\n",
    "# EOF (end-of-file) token is used to indicate that\n",
    "# there is no more input left for lexical analysis\n",
    "INTEGER, PLUS, MINUS, EOF = 'INTEGER', 'PLUS', 'MINUS', 'EOF'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Token(object):\n",
    "    def __init__(self, type, value):\n",
    "        # token type: INTEGER, PLUS, MINUS, or EOF\n",
    "        self.type = type\n",
    "        # token value: non-negative integer value, '+', '-', or None\n",
    "        self.value = value\n",
    "\n",
    "    def __str__(self):\n",
    "        \"\"\"String representation of the class instance.\n",
    "\n",
    "        Examples:\n",
    "            Token(INTEGER, 3)\n",
    "            Token(PLUS '+')\n",
    "        \"\"\"\n",
    "        return 'Token({type}, {value})'.format(\n",
    "            type=self.type,\n",
    "            value=repr(self.value)\n",
    "        )\n",
    "\n",
    "    def __repr__(self):\n",
    "        return self.__str__()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Interpreter(object):\n",
    "    def __init__(self, text):\n",
    "        # client string input, e.g. \"3 + 5\", \"12 - 5\", etc\n",
    "        self.text = text\n",
    "        # self.pos is an index into self.text\n",
    "        self.pos = 0\n",
    "        # current token instance\n",
    "        self.current_token = None\n",
    "        self.current_char = self.text[self.pos]\n",
    "\n",
    "    def error(self):\n",
    "        raise Exception('Error parsing input')\n",
    "\n",
    "    def advance(self):\n",
    "        \"\"\"Advance the 'pos' pointer and set the 'current_char' variable.\"\"\"\n",
    "        self.pos += 1\n",
    "        if self.pos > len(self.text) - 1:\n",
    "            self.current_char = None  # Indicates end of input\n",
    "        else:\n",
    "            self.current_char = self.text[self.pos]\n",
    "\n",
    "    def skip_whitespace(self):\n",
    "        while self.current_char is not None and self.current_char.isspace():\n",
    "            self.advance()\n",
    "\n",
    "    def integer(self):\n",
    "        \"\"\"Return a (multidigit) integer consumed from the input.\"\"\"\n",
    "        result = ''\n",
    "        while self.current_char is not None and self.current_char.isdigit():\n",
    "            result += self.current_char\n",
    "            self.advance()\n",
    "        return int(result)\n",
    "\n",
    "    def get_next_token(self):\n",
    "        \"\"\"Lexical analyzer (also known as scanner or tokenizer)\n",
    "\n",
    "        This method is responsible for breaking a sentence\n",
    "        apart into tokens.\n",
    "        \"\"\"\n",
    "        while self.current_char is not None:\n",
    "\n",
    "            if self.current_char.isspace():\n",
    "                self.skip_whitespace()\n",
    "                continue\n",
    "\n",
    "            if self.current_char.isdigit():\n",
    "                return Token(INTEGER, self.integer())\n",
    "\n",
    "            if self.current_char == '+':\n",
    "                self.advance()\n",
    "                return Token(PLUS, '+')\n",
    "\n",
    "            if self.current_char == '-':\n",
    "                self.advance()\n",
    "                return Token(MINUS, '-')\n",
    "\n",
    "            self.error()\n",
    "\n",
    "        return Token(EOF, None)\n",
    "\n",
    "    def eat(self, token_type):\n",
    "        # compare the current token type with the passed token\n",
    "        # type and if they match then \"eat\" the current token\n",
    "        # and assign the next token to the self.current_token,\n",
    "        # otherwise raise an exception.\n",
    "        if self.current_token.type == token_type:\n",
    "            self.current_token = self.get_next_token()\n",
    "        else:\n",
    "            self.error()\n",
    "\n",
    "    def expr(self):\n",
    "        \"\"\"Parser / Interpreter\n",
    "\n",
    "        expr -> INTEGER PLUS INTEGER\n",
    "        expr -> INTEGER MINUS INTEGER\n",
    "        \"\"\"\n",
    "        # set current token to the first token taken from the input\n",
    "        self.current_token = self.get_next_token()\n",
    "\n",
    "        # we expect the current token to be an integer\n",
    "        left = self.current_token\n",
    "        self.eat(INTEGER)\n",
    "\n",
    "        # we expect the current token to be either a '+' or '-'\n",
    "        op = self.current_token\n",
    "        if op.type == PLUS:\n",
    "            self.eat(PLUS)\n",
    "        else:\n",
    "            self.eat(MINUS)\n",
    "\n",
    "        # we expect the current token to be an integer\n",
    "        right = self.current_token\n",
    "        self.eat(INTEGER)\n",
    "        # after the above call the self.current_token is set to\n",
    "        # EOF token\n",
    "\n",
    "        # at this point either the INTEGER PLUS INTEGER or\n",
    "        # the INTEGER MINUS INTEGER sequence of tokens\n",
    "        # has been successfully found and the method can just\n",
    "        # return the result of adding or subtracting two integers,\n",
    "        # thus effectively interpreting client input\n",
    "        if op.type == PLUS:\n",
    "            result = left.value + right.value\n",
    "        else:\n",
    "            result = left.value - right.value\n",
    "        return result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "calc> 3+5\n",
      "8\n",
      "calc> 3-5\n",
      "-2\n",
      "calc> break\n"
     ]
    },
    {
     "ename": "Exception",
     "evalue": "Error parsing input",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mException\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-5-d9ead8cf481c>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     13\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     14\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m'__main__'\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 15\u001b[1;33m     \u001b[0mmain\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32m<ipython-input-5-d9ead8cf481c>\u001b[0m in \u001b[0;36mmain\u001b[1;34m()\u001b[0m\n\u001b[0;32m      8\u001b[0m             \u001b[1;32mcontinue\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      9\u001b[0m         \u001b[0minterpreter\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mInterpreter\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtext\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 10\u001b[1;33m         \u001b[0mresult\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0minterpreter\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mexpr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     11\u001b[0m         \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mresult\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     12\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m<ipython-input-3-093abaf90d7a>\u001b[0m in \u001b[0;36mexpr\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m     76\u001b[0m         \"\"\"\n\u001b[0;32m     77\u001b[0m         \u001b[1;31m# set current token to the first token taken from the input\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 78\u001b[1;33m         \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcurrent_token\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget_next_token\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     79\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     80\u001b[0m         \u001b[1;31m# we expect the current token to be an integer\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m<ipython-input-3-093abaf90d7a>\u001b[0m in \u001b[0;36mget_next_token\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m     55\u001b[0m                 \u001b[1;32mreturn\u001b[0m \u001b[0mToken\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mMINUS\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'-'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     56\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 57\u001b[1;33m             \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0merror\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     58\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     59\u001b[0m         \u001b[1;32mreturn\u001b[0m \u001b[0mToken\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mEOF\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m<ipython-input-3-093abaf90d7a>\u001b[0m in \u001b[0;36merror\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m     10\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     11\u001b[0m     \u001b[1;32mdef\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 12\u001b[1;33m         \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'Error parsing input'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     13\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     14\u001b[0m     \u001b[1;32mdef\u001b[0m \u001b[0madvance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mException\u001b[0m: Error parsing input"
     ]
    }
   ],
   "source": [
    "def main():\n",
    "    while True:\n",
    "        try:\n",
    "            text = input('calc> ')\n",
    "        except EOFError:\n",
    "            break\n",
    "        if not text:\n",
    "            continue\n",
    "        interpreter = Interpreter(text)\n",
    "        result = interpreter.expr()\n",
    "        print(result)\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
